#!/usr/bin/env php
<?php
$SOAR_DIR=getenv("SOAR_DIR");
if(empty($SOAR_DIR)) $SOAR_DIR="/opt/soar";
$SOAR_DIR="../../../../..";
include "$SOAR_DIR/common/php/soar/soar.inc";

exit(main($argv));

function main($argv)
{/*{{{*/
    if(load_cnfg() == false)
        return 1;
    $daemon=false;
    if(isset($argv[1]))
    {
        if($argv[1] == "-d" || $argv[1] == "--daemon")
           $daemon=true; 
    }
    if(start($daemon) == false)
        return 1;
    return 0;
}/*}}}*/

class Stat
{/*{{{*/
    private static $inst=null;

    private function __construct()
    {/*{{{*/
    }/*}}}*/

    public static function init()
    {/*{{{*/
        if(self::$inst == null)
        {
            self::$inst=new Stat();
        }
    }/*}}}*/

    public static function getInstance()
    {/*{{{*/
        return self::$inst;
    }/*}}}*/

    public $client=array();  // ip:port => call_num
    public $client_conn_num=0;
    public $svc=array();     // name => call_num
}/*}}}*/

function load_cnfg()
{/*{{{*/
    $cnfg_file=dirname(__FILE__)."/server.cnfg.inc";
    include $cnfg_file;

    if(empty(Cnfg::$access['server_port']) ||
            Cnfg::$access['server_port'] < 1024)
    {
        printf("%s: access.server_port invalid\n", __METHOD__);
        return false;
    }
    if(empty(Cnfg::$access['monitor_port']) ||
            Cnfg::$access['monitor_port'] < 1024)
    {
        printf("%s: access.monitor_port invalid\n", __METHOD__);
        return false;
    }

    return true;
}/*}}}*/

function start($daemon)
{/*{{{*/
    if($daemon)
    {
        $pid=pcntl_fork();
        if($pid == -1)
        {
            printf("%s: daemonize fail", __METHOD__);
            return false;
        }
        if($pid > 0)
        {
            return true;
        }
        // Errlog
        if(Errlog::init(basename(__FILE__), Cnfg::$errlog['location'])
                == false)
        {
            printf("%s: init errlog fail", __METHOD__);
            return false;
        }
    }

    // setuid
    if(posix_getuid() == 0)
    {
        if(!empty(Cnfg::$user))
        {
            $user=posix_getpwnam(Cnfg::$user);
            if($user)
            {
                if(posix_setuid($user['uid']) == false)
                {
                    printf("Change to user %s fail\n", Cnfg::$user);
                    return false;
                }
                printf("Change to user %s\n", Cnfg::$user);
            }
            else
            {
                printf("User %s not found, keep root\n", Cnfg::$user);
            }
        }
        else
        {
            printf("User empty, keep root\n");
        }
    }

    Stat::init();

    return worker::start();
}/*}}}*/

class Worker
{/*{{{*/
    public static function start()
    {/*{{{*/
        // 载入服务
        $svc_dir="./svc";
        include_once "$svc_dir/svr.inc";
        $dh=opendir($svc_dir);
        while($inc=readdir($dh))
        {
            if(substr($inc, 0, 1) != "." &&
                    substr($inc, -4) == ".inc" &&
                    $inc != "svr.inc")
            {
                include_once "$svc_dir/$inc";
                printf("load $inc\n");
            }
        }

        $ep=new Ep();
        $sock=Sock::listen(Cnfg::$access['monitor_port']);
        if($sock === false)
        {
            Errlog::add("%s: open monitor_port(%d) fail",
                    __METHOD__, Cnfg::$access['monitor_port']);
            return false;
        }
        $monitor_slot_id=$ep->addListen($sock);
        if($monitor_slot_id === false)
        {
            Errlog::add("%s: Ep::addListen monitor fail", __METHOD__);
            return false;
        }
        printf("open monitor port:%d slot:%d\n",
                Cnfg::$access['monitor_port'], $slot_id);
        Errlog::add("%s: open monitor port %d",
                __METHOD__, Cnfg::$access['monitor_port']);

        $sock=Sock::listen(Cnfg::$access['server_port']);
        if($sock === false)
        {
            printf("%s: open server_port(%d) fail",
                    __METHOD__, Cnfg::$access['server_port']);
            return false;
        }
        $server_slot_id=$ep->addListen($sock);
        if($server_slot_id === false)
        {
            Errlog::add("%s: Ep::addListen server fail", __METHOD__);
            return false;
        }
        printf("open server port:%d slot:%d\n",
                Cnfg::$access['server_port'], $slot_id);
        Errlog::add("%s: open server port %d",
                __METHOD__, Cnfg::$access['server_port']);


        $worker=new Worker($ep, $monitor_slot_id, $server_slot_id);
        $worker->loop();
        return true;
    }/*}}}*/

    public function __construct($ep, $monitor_slot_id, $server_slot_id)
    {/*{{{*/
        $this->ep=$ep;
        $this->listen_slots['monitor']=$monitor_slot_id;
        $this->listen_slots['server']=$server_slot_id;
    }/*}}}*/

    private $ep=null;
    private $listen_slots=array();    // 'server'=> 'monitor'=>
    
    private $quit=false;
    private $monitor_login=-1;
    private $cmd_map=array(
            array('name'=>"printclient", 'ab'=>"pclt",
                'desc'=>"print client ip & port",
                'usage'=>"printclient(pwrk)"),
            array('name'=>"printservice", 'ab'=>"psvc",
                'desc'=>"print service status",
                'usage'=>"printservice(psvc)"),
            array('name'=>"shutdown", 'ab'=>"shutdown",
                'desc'=>"shutdown server",
                'usage'=>"shutdown"),
            array('name'=>"help", 'ab'=>"h",
                'desc'=>"",
                'usage'=>"help(h) [command]")
                );

    private function loop()
    {/*{{{*/
        while(!$this->quit)
        {
            $active_list=$this->ep->poll();
            if($active_list === false)
            {
                Errlog::add("%s: ep::poll fail", __METHOD__);
            }
            else
            {
                foreach($active_list as $active)
                {
                    //print_r($active);
                    switch($active['type'])
                    {
                        case ep::CONN:
                            self::conn($active);
                            break;
                        case ep::RECV:
                            self::recv($active);
                            break;
                        case ep::ABORT:
                        case ep::TIMEOUT:
                            self::disconn($active);
                            break;
                    }
                }
            }
        }
    }/*}}}*/

    private function conn($active)
    {/*{{{*/
        $CONSOLE_IDLE_TIMEOUT=300;
        $CONN_IDLE_TIMEOUT=120;

        if($active['slot_id'] == $this->listen_slots['monitor'])
        {
            if($this->monitor_login >= 0)
            {
                Errlog::add("%s: monitor has logged in", __METHOD__);
                socket_close($active['sock']);
                return false;
            }
        }
        $new_slot=$this->ep->add($active['new_sock']);
        if($new_slot === false)
        {
            Errlog::add("%s: ep::add fail", __METHOD__);
            socket_close($active['new_sock']);
            return false;
        }
        if($active['slot_id'] == $this->listen_slots['monitor'])
        {
            $this->ep->setTimeout($new_slot, $CONSOLE_IDLE_TIMEOUT);
            $this->monitor_login=$new_slot;
            Errlog::add("%s: monitor log in", __METHOD__);
        }
        else
        {
            $this->ep->setTimeout($new_slot, $CONN_IDLE_TIMEOUT);
            $this->ep->setWrap($new_slot);
            $slot_info=$this->ep->getSlotInfo($new_slot);
            $addr=$slot_info['peer_ip'].":".$slot_info['peer_port'];
            $stat=Stat::getInstance();
            $stat->client[$addr]=0;
            ++$stat->client_conn_num;
            //print "conn:$new_slot\n";
        }
        return true;
    }/*}}}*/

    private function recv($active)
    {/*{{{*/
        if($active['slot_id'] == $this->monitor_login)
        {
            $response=self::callcmd($active['msg']);
        }
        else
        {
            $response=self::callsvc($active['msg']);
        }
        $res=$this->ep->send($active['slot_id'], $response);
        if($res == false)
        {
            Errlog::add("%s: send fail", __METHOD__); 
        }
    }/*}}}*/

    private function disconn($active)
    {/*{{{*/
        if($active['slot_id'] == $this->monitor_login)
        {
            $this->monitor_login=-1;
            Errlog::add("%s: monitor %s revoke, error:%d",
                    __METHOD__, $active['addr'], $active['error']);
        }
        else
        {
            $stat=Stat::getInstance();
            if(array_key_exists($active['addr'], $stat->client))
                unset($stat->client[$active['addr']]);
            Errlog::add("%s: client %s %s, error:%d, total:%d, ",
                    __METHOD__, $active['addr'],
                    ($active['type']==ep::ABORT)?"abort":"timeout",
                    $active['error'],
                    count($stat->client));
        }
        return true;
    }/*}}}*/

    private function callcmd($request)
    {/*{{{*/
        $request=trim($request);
        $pos=strpos($request, " ");
        if($pos === false)
        {
            $cmd=$request;
            $body="";
        }
        else
        {
            $cmd=substr($request, 0, $pos);
            $body=substr($request, $pos);
            $body=ltrim($body);
        }
        $cmd=strtolower($cmd);
        //printf("cmd(%s)\n", $cmd);
        $cmdsize=strlen($cmd);
        if(0 < $cmdsize && $cmdsize <= misc::NAME_SIZE) 
        {
            $found=false;
            foreach($this->cmd_map as $arr)
            {
                if($arr['name'] == $cmd || $arr['ab'] == $cmd)
                {
                    $found=true;
                    $cmd=$arr['name'];
                    break;
                }
            }
            if($found)
            {
                $func="CMD_FUNC_".strtoupper($cmd);
                $response=self::$func($body);
            }
            else
            {
                $response="command(".$cmd.") not found\n";
            }
        }
        else
        {
            $response="command invalid\n";
        }

        return $response;
    }/*}}}*/

    private function CMD_FUNC_PRINTCLIENT($request)
    {/*{{{*/
        $stat=Stat::getInstance();

        $response=
            "Client                         Calls\n".
            "------------                   --------\n";
        foreach($stat->client as $addr=>$call_num)
        {
            $response.=sprintf("%-30s %8d\n",
                    $addr, $call_num);
        }
        $response.=
            "----------------------------------------\n";
        $response.="Connected: ".$stat->client_conn_num."\n";

        return $response;
    }/*}}}*/

    private function CMD_FUNC_PRINTSERVICE($request)
    {/*{{{*/
        $stat=Stat::getInstance();

        $response=
            "Service                        Calls\n".
            "-------                        ---------\n";
        foreach($stat->svc as $name=>$call_num)
        {
            $response.=sprintf("%-30s  %9d\n",
                    $name, $call_num);
        }
        return $response;
    }/*}}}*/

    private function CMD_FUNC_SHUTDOWN($request)
    {/*{{{*/
        $this->quit=true;
        $response="Shutdown complete\nDisconnect\n";
        return $response;
    }/*}}}*/

    private function CMD_FUNC_HELP($request)
    {/*{{{*/
        if(empty($request))
        {
            $response="help [command]\ncommands:\n";
            foreach($this->cmd_map as $arr)
            {
                $response.="    ".$arr['name']."\n";
            }
            return $response;
        }

        $request=strtolower($request);
        foreach($this->cmd_map as $arr)
        {
            if($arr['name'] == $request || $arr['ab'] == $request)
            {
                $response="Description: ".$arr['desc']."\n".
                    "Usage: ".$arr['usage']."\n";
                return $response;
            }
        }
        $response="Command not found, type help\n";
        return $response;
    }/*}}}*/

    private function callsvc($request_s)
    {/*{{{*/
        $request=json_decode($request_s, true);
        if($request == null)
        {
            Errlog::add("%s: request(%s) invalid",
                    __METHOD__, substr($request_s, 0, 99)); 
            return json_encode(array('errcode'=>SOAR_ERR::MSG));
        }

        if(!isset($request['svc']) ||
                strlen($request['svc']) > misc::NAME_SIZE)
        {
            Errlog::add("%s: svc(%s) invalid",
                    __METHOD__,
                    isset($request['svc'])?$request['svc']:'null');
            return json_encode(array('errcode'=>SOAR_ERR::SVC_INVALID));
        }
        $svc=strtoupper($request['svc']);

        if(class_exists($svc))
        {
            $stat=Stat::getInstance();
            $ins=new $svc();
            $response=$ins->call($request['paras']);
            if(array_key_exists($svc, stat::$svc))
                ++$stat->svc[$svc];
            else
                $stat->svc[$svc]=1;
        }
        else
        {
            Errlog::add("%s: class(%s) not found",
                    __METHOD__, $svc);
            $response=array('errcode'=>SOAR_ERR::SVC_NOT_FOUND);
        }

        return json_encode($response);
    }/*}}}*/
}/*}}}*/
?>
